#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "cast.h"
#include "client_list.h"


/* Global constants */
#define MAXBUF 1024
#define PORT 33000
#define BACKLOG 1024

int readline(int fd, void *str, int maxlen){
	int n, rc ;
	char c;
	char *buffer;
	buffer = str;


	for (n = 1; n < maxlen; n++){
		if ( (rc = read(fd, &c, 1) ) == 1 ){
			*buffer ++= c ;
			if ( c == '\n')
				break;
		}

		if ( rc == 0 ) {
			if (n == 1)
				return 0;
			else
				break;
		}

		else if ( rc == -1){
			if (errno == EINTR)
				continue;
			else
				return -1;
		}
	}
	*buffer ='\0';
	return n;
}

int sendline(int fd, const void *str, int maxlen){

	int nleft, nwritten;
	const char* buffer;
	buffer = str;
	nleft=maxlen;

	while ( nleft > 0 ){
		if ( (nwritten = write (fd,buffer,nleft)) <= 0 ){
			if (errno == EINTR)
				nwritten = 0;
			else return 1;
		}
		nleft -= nwritten;
		buffer += nwritten;
	}
	return maxlen;
}


int main(int argc, char *argv[]) {

	int list_s; // listening socket
	int conn_s;    // connection socket
	short int port; //port number
	struct sockaddr_in servaddr; // socket address struct
	char buffer[MAXBUF]; //character buffer
	char *endptr; // for strtol()



	/*Get port number from the command line, and set to default port if no arguments were supplied*/

	if ( argc == 2){

		port = strtol(argv[1], &endptr, 0);

		if (*endptr){

			fprintf(stderr, "ECHOSERV :Invalid Port number.\n");
			exit(EXIT_FAILURE);

		}
	}

	else if (argc < 2) {
		port =PORT;
	}

	else {
		fprintf(stderr, "ECHOSERV :Invalid Invalid arguments.\n");
		exit(EXIT_FAILURE);
	}
	/* Create the listening socket */

	if ( (list_s = socket(AF_INET,SOCK_STREAM,0)) < 0 ){
		fprintf(stderr, "ECHOSERV :Error creating listening socket.\n");

		exit( EXIT_FAILURE );
	}

	/* Set all bytes in socket address structure to
     zero, and fill in the relevant data members */

	memset(&servaddr, 0, sizeof (servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	int optval=1;
	setsockopt(list_s,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));

	/*
    Bind our socket address to the
    listening socket, and call listen()*/

	if (bind( list_s,(struct sockaddr *) &servaddr,sizeof(servaddr)) < 0 ){
		fprintf(stderr, "ECHOSERV :Error calling binding().\n");
		exit( EXIT_FAILURE );
	}
	 /* get assigned port */
//	  struct sockaddr_in sock_info;
//	  socklen_t size = sizeof(sock_info);
//	  getsockname(list_s, (struct sockaddr *) &sock_info, &size);
//	  fprintf(stdout,"port : %d\n", ntohs(sock_info.sin_port));



	if (listen( list_s, BACKLOG) < 0 ){
		fprintf(stderr, "ECHOSERV :Error calling listen().\n");
		exit( EXIT_FAILURE );
	}


	fd_set readfds;  // On définit l'ensemble des fd à surveiller
	fd_set tmpfds;   // On créé un tableau qui lui ne sera jamais modifié pour se protéger du select
	fd_set writefds, tmpwritefds;
	fd_set errorfds, tmperrorfds;

	struct timeval timeval;
	timeval.tv_sec=1000;
	timeval.tv_usec=0;

	int size_servaddr;
	int fd;
	int selector;

	/*FD_ZERO() clears out the different fd sets,so that
	it doesn ’t contain any file descriptors. */

	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&errorfds);

	/* We initially have only one socket open,
	to receive new incoming connections.
	Initialise accordingly
	the tmp set with select MACRO */

	FD_SET(list_s, &readfds);   // Ajout du serveur dans l'ensemble des fd à surveiller


	/* calculate size of file descriptors table */
	int max = list_s;// Initialisation arbitraire de la valeur max des fd


	/* Enter an infinite loop to respond
	to client requests and echo input */

	fprintf(stdout, "ECHOSERV is waiting connections.\n");

	//New client_list

	client_list client_list=new_client_list();

	while ( 1 ) {

		//Surveillance etape 1

		tmpfds = readfds;//faire des copies des ensembles de descripteurs avant d'appeller select
		tmpwritefds = writefds;
		tmperrorfds = errorfds;

		/* The select() system call waits until any of
		the file descriptors specified in the read,
		sets given to it, is ready to give data */


		if(selector < 0)
			perror("select error");


		do {
			selector=select( max+1, &readfds , &writefds, &errorfds,&timeval);
		}
		while(-1 == selector && errno ==EINTR);

		if(selector == 0){
			continue;
		}
		/* Make the diffellist=rence between a new client connection and data received from an already existing client */

		else if(selector > 0) {
			if(FD_ISSET(list_s, &readfds)!=0) {

				size_servaddr=sizeof(servaddr);
				conn_s = accept(list_s, (struct sockaddr *)&servaddr, (socklen_t*)&size_servaddr);
				if (conn_s < 0 ){
					fprintf(stderr, "ECHOSERV: Error calling accept().\n");
					exit( EXIT_FAILURE );}


				if( conn_s > max ){

					max = conn_s;   // Definition de la nouvelle valeur max
					printf("nouveau max %d\n", max);
					printf("nouveau client sur socket %d\n", conn_s);


					//Stockage des pseudos
					char temp[]="What is your name ?\n";
					sendline(conn_s,temp,sizeof(temp));

					if( client_list != NULL){
						fprintf(stdout,"List not Null\n");
						list_display(client_list,conn_s);
					}


					char name[20];
					readline(conn_s,name,sizeof(name));
					search_delete(name);


					//t_client client_new=new_client(name,max);// Set new client
					//fprintf(stdout,"Name client: %s\n",get_name(client_new));


					add_client_list(client_list,get_name(new_client(name,max)),get_number_socket(new_client(name,max)));
					add_client_list(client_list,get_name(new_client(name,max)),125);
					add_client_list(client_list,"Bob",45);


					list_display(client_list,max);

					char name1[40];
					sprintf(name1,"Your name is %s\n(start) %s %d> ",name,name,max-3);
					sendline(conn_s,name1,strlen(name1));
				}

				FD_SET(conn_s,&tmpfds); // Ajout du client dans l'ensemble des fd a surveiller


			}


			for(fd=4; fd <= max; fd++){
				t_client client_tmp;
				client_tmp=find_client(client_list,fd);

				if( FD_ISSET(fd,&readfds) != 0 && fd != list_s){

					//Retrieve an input line from the connected socket then simply write it back to the same socket.

					char usertmp[40];

					sprintf(usertmp,"%s %d ligne 269>",get_name(client_tmp),fd-3);

					sendline(conn_s,usertmp,strlen(usertmp));

					//Lecture du message
					readline(fd, buffer, MAXBUF-1);
					search_delete(buffer);


					// Commande pour obtenir tous les pseudos

					if (strcmp(buffer,"getusers") == 0)
						list_display(client_list,fd);

					// Commande pour envoyer à un particulier en unicast
					if (strcmp(buffer,"Send Message") == 0)
						unicast(fd,buffer,client_list);

					//Ecriture des (pseudos)
					char* user;

					if (!is_empty(client_list) && client_tmp != NULL){
						user=client_tmp->nameclient;
						fprintf(stdout, " ligne 301  %s has sent a message\n",user);
					}
					else
						fprintf(stdout, "Client is NULL ligne 298\n");






					//Envoi en Broadcast aux autres utilisateurs et au serveur
					int i;
					for (i=4; i<= max;i++){
						t_client client_tmp= find_client(client_list,i);
						char* user =get_name(client_tmp);

						if (i!= fd){

							char tmp[MAXBUF];
							sprintf(tmp," \n ligne 316 %s %d >",user,fd-3);
							strcat(tmp,buffer);
							sendline(i,tmp, strlen(tmp));


						}
					}

					//FD_CLR(fd,&readfds);

					//}


				}


				/*error or connection closed by the client */
				/* Close the connected socket*/
				//								if ( close(fd) < 0 ) {
				//									fprintf(stderr,"ECHOSERV : Error calling close()\n");
				//									exit(EXIT_FAILURE);
				//								}


			}




		}

		readfds = tmpfds;
		writefds = tmpwritefds;
		errorfds = tmperrorfds;


	}


	return 0;
}





